﻿
package extremefx.i18n {
	import extremefx.i18n.locales.Invariant;	
	import extremefx.DateTime;
	import extremefx.events.EventHandler;
	import extremefx.i18n.locales.EN_US;
	import extremefx.tools.getClassByName;	

	/**
	 * @author Marcelo Volmaro
	 */
	public class Culture implements IFormatProvider {
		private static var _invariantCulture:Culture;
		private static var _currentCulture:Culture;
		private static var _cids:Vector.<uint> = Vector.<uint>([1078,14337,15361,5121,3073,2049,11265,13313,12289,4097,6145,8193,16385,1025,10241,7169,9217,1146,2092,1068,1059,1026,8218,5146,1027,1029,1106,1030,3079,2055,1031,5127,4103,1125,1032,9225,3081,10249,4105,2057,6153,8201,5129,13321,11273,1033,7177,12297,11274,16394,13322,9226,5130,7178,12298,3082,4106,18442,2058,19466,6154,10250,20490,15370,17418,14346,8202,1061,1069,1065,1035,1124,1080,2060,3084,4108,1036,5132,6156,1122,2108,1110,1095,1037,1081,4122,1050,1038,1067,1057,1039,2064,1040,2141,1041,1079,1087,1099,1042,1111,1088,1134,1063,1062,1153,1071,1104,1148,1102,2110,1086,1082,1044,2067,1043,2068,1132,1094,1045,1046,2070,1131,2155,3179,1047,1048,1049,1103,3131,1083,2107,1051,1060,6203,7227,4155,5179,9275,8251,1052,7194,3098,6170,2074,2077,1053,1089,1114,1097,1098,1054,1074,1055,1092,1058,1056,2115,1091,1066,1076,2052,3076,5124,4100,1028,1077]);
		private static var _cnms:Vector.<String> = Vector.<String>(["af-ZA","ar-AE","ar-BH","ar-DZ","ar-EG","ar-IQ","ar-JO","ar-KW","ar-LB","ar-LY","ar-MA","ar-OM","ar-QA","ar-SA","ar-SY","ar-TN","ar-YE","arn-CL","az-Cyrl-AZ","az-Latn-AZ","be-BY","bg-BG","bs-Cyrl-BA","bs-Latn-BA","ca-ES","cs-CZ","cy-GB","da-DK","de-AT","de-CH","de-DE","de-LI","de-LU","dv-MV","el-GR","en-029","en-AU","en-BZ","en-CA","en-GB","en-IE","en-JM","en-NZ","en-PH","en-TT","en-US","en-ZA","en-ZW","es-AR","es-BO","es-CL","es-CO","es-CR","es-DO","es-EC","es-ES","es-GT","es-HN","es-MX","es-NI","es-PA","es-PE","es-PR","es-PY","es-SV","es-UY","es-VE","et-EE","eu-ES","fa-IR","fi-FI","fil-PH","fo-FO","fr-BE","fr-CA","fr-CH","fr-FR","fr-LU","fr-MC","fy-NL","ga-IE","gl-ES","gu-IN","he-IL","hi-IN","hr-BA","hr-HR","hu-HU","hy-AM","id-ID","is-IS","it-CH","it-IT","iu-Latn-CA","ja-JP","ka-GE","kk-KZ","kn-IN","ko-KR","kok-IN","ky-KG","lb-LU","lt-LT","lv-LV","mi-NZ","mk-MK","mn-MN","moh-CA","mr-IN","ms-BN","ms-MY","mt-MT","nb-NO","nl-BE","nl-NL","nn-NO","ns-ZA","pa-IN","pl-PL","pt-BR","pt-PT","quz-BO","quz-EC","quz-PE","rm-CH","ro-RO","ru-RU","sa-IN","se-FI","se-NO","se-SE","sk-SK","sl-SI","sma-NO","sma-SE","smj-NO","smj-SE","smn-FI","sms-FI","sq-AL","sr-Cyrl-BA","sr-Cyrl-CS","sr-Latn-BA","sr-Latn-CS","sv-FI","sv-SE","sw-KE","syr-SY","ta-IN","te-IN","th-TH","tn-ZA","tr-TR","tt-RU","uk-UA","ur-PK","uz-Cyrl-UZ","uz-Latn-UZ","vi-VN","xh-ZA","zh-CN","zh-HK","zh-MO","zh-SG","zh-TW","zu-ZA"]);
		
		private var _textInfo:TextInfo;
		protected var _pluralForm:Function;
		private var _locale:Invariant;
		private var _optionalCalendars:Array;

		/**
		 * Provides information about a specific culture, such as the names of the culture, the writing system, the calendar used, and how to format dates and sort strings.
		 */
		public function Culture(pName:* = "") {
			_pluralForm = _twoForm;
			
			var klass:Class = null;
			
			if (pName is Number || pName is uint || pName is int){
				klass = _getLocaleInfoByName(String(_cnms[_cids.indexOf(id)]));
				
			} else if (pName is String){
				var id:uint = parseInt(String(pName));
				if (isNaN(id)){
					klass = _getLocaleInfoByName(String(pName));
					
				} else {
					klass = _getLocaleInfoByName(String(_cnms[_cids.indexOf(id)]));
				}
				
			} else if (pName is Class){
				klass = pName as Class;
			}
			
			if (klass == null) {
				throw new ArgumentError("pName");
			}
			
			_locale = new klass();

			if (_locale._calendar != -1) _locale._dateTimeFormat.calendar = _getCalendarById(_locale._calendar);
			
			//force the inclusion of this two...
			var a:Function = XDate.toString;
			a = XNumber.toString;
		}

		private function _getLocaleInfoByName(pName:String):Class {
			return getClassByName("extremefx.i18n.locales." + (pName.replace("-", "_").toUpperCase()));
		}
		
		/**
		 * Gets the culture name in the format "[languagecode2]-[country/regioncode2]".
		 * The culture name in the format "[languagecode2]-[country/regioncode2]", where [languagecode2] is a lowercase two-letter code derived from ISO 639-1 and [country/regioncode2] is an uppercase two-letter code derived from ISO 3166.
		 */
		public function get name():String{
			return _locale._code;
		}

		/**
		 * Gets the culture identifier for the current Culture. 
		 */
		public function get id():Number{
			return _locale._id;
		}

		/**
		 * Gets or sets a NumberFormatInfo that defines the culturally appropriate format of displaying numbers, currency, and percentage.
		 */
		public function get numberFormat():NumberFormatInfo {
			return _locale._numberFormat;
		}

		public function set numberFormat(pI:NumberFormatInfo):void {
			if (pI is NumberFormatInfo) _locale._numberFormat = pI;
		}

		/**
		 * Gets or sets a DateTimeFormatInfo that defines the culturally appropriate format of displaying dates and times.
		 */
		public function get datetimeFormat():DateTimeFormatInfo {
			return _locale._dateTimeFormat;
		}

		public function set datetimeFormat(pI:DateTimeFormatInfo):void {
			if (pI is DateTimeFormatInfo) _locale._dateTimeFormat = pI;
		}

		/**
		 * Gets the CultureInfo that is culture-independent (invariant).
		 */
		public static function get INVARIANT_CULTURE():Culture{
			if (_invariantCulture == null) _invariantCulture = new Culture();
			return _invariantCulture;
		}

		/**
		 * Gets the CultureInfo that represents the culture used by the current running program.
		 */
		public static function get currentCulture():Culture {
			if (_currentCulture == null) _currentCulture = new Culture(EN_US);
			return _currentCulture;
		}

		public static function set currentCulture(_pC:Culture):void{
			if (_pC is Culture) {
				_currentCulture = _pC;
				onCultureChange.fire(new CultureChangeEventArgs(_pC));
			}
		}
		
		/**
		 * Event Handler. Fires every time the culture changes.
		 */
		private static var _eOnCultureChange:EventHandler;
		public static function get onCultureChange():EventHandler {
			if (_eOnCultureChange == null) _eOnCultureChange = new EventHandler(Culture);
			return _eOnCultureChange;
		}
		
		/**
		 * Gets the TextInfo that defines the writing system associated with the culture.
		 */
		public function get textInfo():TextInfo {
			if (_textInfo == null) {
				_textInfo = new TextInfo(this);
			}

			return _textInfo;
		}
		
		/**
		 * Gets the ISO 639-1 two-letter code for the language of the current Culture.
		 */
		public function get twoLetterISOLanguageName():String {
			return _locale._code.split("-")[0];
		}

		/**
		 * Gets the ISO 3166 two-letter code for the country/regioncode2 of the current Culture.
		 */
		public function get twoLetterISOCountryName():String {
			return _locale._code.split("-")[1];
		}


		/**
		 * <code>formatNumberChoice</code> supports the creation of internationalized
		 * messages with plural inflection. It is based on <i>plural selection</i>, 
		 * i.e. the caller specifies messages for each plural case that can appear 
		 * in the users language and the <code>formatNumberChoice</code> function 
		 * selects the appropriate message based on the number.
		 * 
		 * @param pArray [String|Array] if you pass a string, the string must start with [I18N] followed with the plural forms separated by a pipe (|). If you pass an Array, each element of the array corresponds to one plural form.
		 * @param pNumber the number for which the plural message should be formatted.
		 */
		
		public function formatNumberChoice(pArray:*, pNumber:uint):String {
			var a:Array;
			
			if (pArray is String) {
				var s:String = String(pArray);
				if (s.indexOf("[I18N]|") == 0) a = s.split("|").slice(1);
				
			} else if (pArray is Array){
				a = pArray as Array;
				
			} else {
				throw ArgumentError("The specified argument it's not an Array nor a String");
			}
			
			return _pluralForm(a, pNumber);
		}

		/**
		 * Gets an object that defines how to format the specified type.
		 * @param pType The Type for which to get a formatting object. This method only supports the NumberFormatInfo and DateTimeFormatInfo types. 
		 * @return The value of the NumberFormat property, which is a NumberFormatInfo containing 
		 * 			the default number format information for the current Culture, 
		 * 			if pType is the Type object for the NumberFormatInfo class. 
		 * 			-or- 
		 * 			The value of the DateTimeFormat property, which is a DateTimeFormatInfo containing 
		 * 			the default date and time format information for the current Culture, 
		 * 			if pType is the Type object for the DateTimeFormatInfo class. 
		 * 			-or- 
		 * 			a null reference, if pType is any other object. 
		 */
		public function getFormat(pType:*):* {
			if (pType is Date || pType is DateTime) return _locale._dateTimeFormat;
			if (pType is Number || pType is int || pType is uint) return _locale._numberFormat;
			return null;
		}
		
		/**
		 * Gets the default calendar used by the culture.
		 */
		public function get calendar():Calendar {
			return _locale._dateTimeFormat.calendar;
		}

		/**
		 * Gets the list of calendars that can be used by the culture.
		 * @return An array of Calendars that represents the calendars that can be used by the culture represented by the current Culture. 
		 */
		public function get optionalCalendars():Array {
			if (_optionalCalendars == null){
				if (_locale._calendars == null){
					_optionalCalendars = [new GregorianCalendar(GregorianCalendarTypes.LOCALIZED)];

				} else {
					_optionalCalendars = [];
					for (var i:uint = 0; i<_locale._calendars.length; i++){
						_optionalCalendars.push(_getCalendarById(_locale._calendars[i]));
					}
				}
			}

			return _optionalCalendars;
		}
		
		

		/**
		 * Gets the culture name in the format "[languagefull] ([country/regionfull])" in English.
		 */
		public function get englishName():String {
			return _locale._englishName;
		}

		/**
		 * Gets the culture name in the format "[languagefull] ([country/regionfull])" in the language of the localized version.
		 */
		public function get displayName():String {
			return _locale._displayName;
		}

		/**
		 * Only one form: Some languages only require one single form.
		 * There is no distinction between the singular and plural form.
		 * Japanese, Korean, Vietnamese, Turkish
		 * Array indexes: 0 for the single form string
		 */
		protected static function _oneForm(pStrings:Array, pVal:uint):String {
			return pStrings[0];
		}

		/**
		 * Two forms, singular used for one only.
		 * This is the form used in most existing programs since it is what English is using.
		 * Danish, Dutch, English, Faroese, German, Norwegian, Swedish
		 * Estonian, Finnish, Greek, Hebrew, Italian, Portuguese, Spanish, Esperanto
		 * Hungarian
		 * Array indexes: 0 for singular, 1 for plural
		 */
		protected static function _twoForm(pStrings:Array, pVal:uint):String {
			return pStrings[pVal == 1 ? 0 : 1];
		}

		/**
		 * Two forms, singular used for zero and one.
		 * Exceptional case in the language family
		 * French, Brazilian Portuguese
		 * Array indexes: 0 for singular, 1 for plural
		 */
		protected static function _twoFormZero(pStrings:Array, pVal:uint):String {
			return pStrings[pVal > 1 ? 0 : 1];
		}

		/**
		 * Three forms, special case for zero.
		 * Latvian
		 * Array indexes: 0 for singular, 1 for plural, 2 for zero
		 */
		protected static function _threeForm0(pStrings:Array, pVal:uint):String {
			return pStrings[pVal % 10 == 1 && pVal%100 != 11 ? 0 : pVal == 0 ? 2 : 1];
		}

		/**
		 * Three forms, special cases for one and two.
		 * Gaeilge (Irish)
		 * Array indexes: 0 for singular, 1 for two, 2 for plural
		 */
		protected static function _threeForm1(pStrings:Array, pVal:uint):String {
			return pStrings[pVal == 1 ? 0 : pVal == 2 ? 1 : 2];
		}

		/**
		 * Three forms, special case for numbers ending in 00 or [2-9][0-9]
		 * Romanian
		 * Array indexes: 0 for singular, 1 for ending in 00 or [2-9][0-9], 2 for the others
		 */
		protected static function _threeForm2(pStrings:Array, pVal:uint):String {
			return pStrings[pVal == 1 ? 0 : (pVal == 0 || (pVal % 100 > 0 && pVal % 100 < 20)) ? 1 : 2];
		}

		/**
		 * Three forms, special case for numbers ending in 1[2-9]
		 * Lithuanian
		 * Array indexes: 0 for ending in 1, 1 for ending in [2-9], 2 for the others
		 */
		protected static function _threeForm3(pStrings:Array, pVal:uint):String {
			return pStrings[pVal % 10 == 1 && pVal % 100 != 11 ? 0 : pVal % 10 >= 2 && (pVal % 100 < 10 || pVal % 100 >= 20) ? 1 : 2];
		}

		/**
		 * Three forms, special cases for numbers ending in 1 and 2, 3, 4, except those ending in 1[1-4]
		 * Croatian, Serbian, Russian, Ukrainian
		 * Array indexes: 0 for ending in 1, 1 for ending in [2-9], 2 for the others
		 */
		protected static function _threeForm4(pStrings:Array, pVal:uint):String {
			return pStrings[pVal % 10 == 1 && pVal % 100 != 11 ? 0 : pVal %10 >=2 && pVal % 10 <= 4 && (pVal % 100 < 10 || pVal % 100 >= 20) ? 1 : 2];
		}

		/**
		 * Three forms, special cases for 1 and 2, 3, 4
		 * Slovak, Czech
		 * Array indexes: 0 for 1, 1 for [2-4], 2 for the others
		 */
		protected static function _threeForm5(pStrings:Array, pVal:uint):String {
			return pStrings[pVal == 1 ? 0 : (pVal >= 2 && pVal <=4 ) ? 1 : 2];
		}

		/**
		 * Three forms, special case for one and some numbers ending in 2, 3, or 4
		 * Polish
		 * Array indexes: 0 for 1, 1 some numbers ending in 2, 3, or 4, 2 for the others
		 */
		protected static function _threeForm6(pStrings:Array, pVal:uint):String {
			return pStrings[pVal == 1 ? 0 : pVal % 10 >= 2 && pVal % 10 <= 4 && (pVal % 100 < 10 || pVal % 100 >= 20) ? 1 : 2];
		}

		/**
		 * Four forms, special case for one and all numbers ending in 02, 03, or 04
		 * Slovenian
		 * Array indexes: 0 for ending in 01, 1 for ending in 02, 2 for ending in 03 or 04, 3 for the others.
		 */
		protected static function _fourForm(pStrings:Array, pVal:uint):String {
			return pStrings[pVal % 100 == 1 ? 0 : pVal % 100 == 2 ? 1 : pVal % 100 == 3 || pVal % 100 == 4 ? 2 : 3];
		}
		
		protected function _getCalendarById(pID:uint):Calendar {
			var name:String;
			
			switch (pID){
				case 1:
				case 2:
				case 9:
				case 10:
				case 11:
				case 12:
					return new GregorianCalendar(pID);
				break;

				case 3:
					name = "HijriCalendar";
				break;

				case 4:
					name = "ThaiBuddhistCalendar";
				break;

				case 5:
					name = "HebrewCalendar";
				break;

				case 6:
					name = "JapaneseCalendar";
				break;

				case 7:
					name = "JulianCalendar";
				break;

				case 8:
					name = "KoreanCalendar";
				break;

				case 13:
					name = "TaiwanCalendar";
				break;

				case 14:
					name = "PersianCalendar";
				break;

				case 15:
					name = "UmAlQuraCalendar";
				break;
			}
			
			try {
				return (getClassByName("extremefx.i18n." + name) as Calendar);
				
			} catch (e:Error){
				throw new ArgumentError ("Calendar" + name + " has not been included at compile time.", name);
			}
			
			return null;
		}
	}
}